/*
 * TODO: Allow to navigate pads the same way we can navigate pins
 * TODO: ditto for silkscreen and assy markings
 */

#include "packageviewer.h"

#include "Ipc2581.h"
#include "GraphicsItemFactory.h"
#include "Layer.h"
#include "layerlistmodel.h"
#include "Package.h"
#include "PackageDetailModel.h"
#include "packagelistmodel.h"
#include "Pin.h"
#include "PinDetailModel.h"
#include "PinListModel.h"
#include "Solarized.h"
#include "graphicsscene.h"
#include "graphicsview.h"
#include "graphicsitem.h"
#include "LayerListWidget.h"

#include <QDebug>
#include <QHeaderView>
#include <QGraphicsDropShadowEffect>
#include <QGraphicsEllipseItem>
#include <QGraphicsItemGroup>
#include <QGraphicsSimpleTextItem>
#include <QGraphicsPathItem>
#include <QListView>
#include <QTableView>
#include <QVBoxLayout>

// Layers in top-bottom order
enum
{
    AssemblyLayer = 0,
    PinLayer,
    SilkscreenLayer,
    LandPatternLayer,
    OutlineLayer,
    LayerCount
};


PackageViewer::PackageViewer(QObject *parent):
    IViewer(parent),
    m_ipc(nullptr),
    m_graphicsItemFactory(new GraphicsItemFactory(this)),
    m_graphicsScene(new GraphicsScene(this)),
    m_graphicsView(new GraphicsView),
    m_packageListView(new QListView),
    m_packageListModel(new PackageListModel(this)),
    m_packageDetailView(new QTableView),
    m_packageDetailModel(new PackageDetailModel(this)),
    m_currentPackage(nullptr),
    m_layerListWidget(new LayerListWidget),
    m_layerListModel(new LayerListModel(this)),
    m_pinListView(new QTableView),
    m_pinListModel(new PinListModel(this)),
    m_pinDetailView(new QTableView),
    m_pinDetailModel(new PinDetailModel(this)),
    m_currentPin(nullptr)
{
    m_layerList = createLayerList();
    m_layerListModel->setLayerList(m_layerList);
    m_layerListModel->setLayerColor(OutlineLayer, Solarized::violet);
    m_layerListModel->setLayerColor(LandPatternLayer, Solarized::red);
    m_layerListModel->setLayerColor(SilkscreenLayer, Solarized::yellow);
    m_layerListModel->setLayerColor(PinLayer, Solarized::cyan);
    m_layerListModel->setLayerColor(AssemblyLayer, Solarized::green);
    m_layerListModel->setAllLayerVisibility(true);
    m_layerListWidget->setLayerListModel(m_layerListModel);

    m_packageListView->setModel(m_packageListModel);
    m_packageListView->setAlternatingRowColors(true);

    m_packageDetailView->setModel(m_packageDetailModel);
    m_packageDetailView->setAlternatingRowColors(true);
    m_packageDetailView->horizontalHeader()->setStretchLastSection(true);
    m_packageDetailView->setSelectionBehavior(QTableView::SelectRows);

    m_pinListView->setModel(m_pinListModel);
    m_pinListView->setAlternatingRowColors(true);
    m_pinListView->horizontalHeader()->setStretchLastSection(true);
    m_pinListView->setSelectionBehavior(QTableView::SelectRows);

    m_pinDetailView->setModel(m_pinDetailModel);
    m_pinDetailView->setAlternatingRowColors(true);
    m_pinDetailView->horizontalHeader()->setStretchLastSection(true);
    m_pinDetailView->setSelectionBehavior(QTableView::SelectRows);

    m_graphicsScene->setLayerModel(m_layerListModel);
    m_graphicsView->setScene(m_graphicsScene);
    m_graphicsView->setBackgroundBrush(Solarized::backgroundHighlight);
    m_graphicsView->setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
    m_graphicsView->setResizeAnchor(QGraphicsView::AnchorUnderMouse);

    m_navigationWidget = new QWidget;
    m_navigationWidget->setLayout(new QVBoxLayout);
    m_navigationWidget->layout()->addWidget(m_packageListView);
    m_navigationWidget->layout()->addWidget(m_packageDetailView);

    m_auxiliaryWidget = new QWidget;
    m_auxiliaryWidget->setLayout(new QVBoxLayout);
    m_auxiliaryWidget->layout()->addWidget(m_layerListWidget);
    m_auxiliaryWidget->layout()->addWidget(m_pinListView);
    m_auxiliaryWidget->layout()->addWidget(m_pinDetailView);

    connect(m_layerListWidget, &LayerListWidget::currentLayerChanged,
            m_graphicsScene, QOverload<const QModelIndex&>::of(&GraphicsScene::setCurrentLayer));
    connect(m_layerListWidget, &LayerListWidget::graphicsViewOrientationChanged,
            m_graphicsView, &GraphicsView::setOrientation);
    connect(m_graphicsView, &GraphicsView::orientationChanged,
            m_layerListWidget, &LayerListWidget::setGraphicsViewOrientation);
    connect(m_layerListWidget, &LayerListWidget::graphicsViewRotationChanged,
            m_graphicsView, &GraphicsView::setRotation);
    connect(m_graphicsView, &GraphicsView::rotationChanged,
            m_layerListWidget, &LayerListWidget::setGraphicsViewRotation);

    connect(m_packageListView->selectionModel(), &QItemSelectionModel::currentRowChanged,
            this, [this](const QModelIndex &current, const QModelIndex &)
    {
        setCurrentPackage(m_packageListModel->package(current));
    });
    connect(m_pinListView->selectionModel(), &QItemSelectionModel::currentRowChanged,
            this, [this](const QModelIndex &current, const QModelIndex&)
    {
        setCurrentPin(m_pinListModel->pin(current));
    });
}

void PackageViewer::setCurrentPackage(Ipc2581b::Package *package)
{
    if (!m_packageList.contains(package))
        return;

    m_currentPackage = package;
    m_packageDetailModel->setPackage(m_currentPackage);

    // Clear graphics scene
    m_graphicsScene->clearAll();

    // Load graphics layers
    populateOutlineLayer();
    populateLandPatternLayer();
    populateSilkscreenLayer();
    populatePinLayer();
    populateAssemblyDrawingLayer();

    // auto-adjust scene rect
    auto itemsRect = m_graphicsScene->layersBoundingRect();
    itemsRect.moveCenter(QPointF());
    auto sceneRect = QTransform::fromScale(10, 10).map(itemsRect).boundingRect();
    m_graphicsScene->setSceneRect(sceneRect);

    auto item = m_graphicsItemFactory->createItem(m_currentPackage->outline);
    m_graphicsScene->addBackgroundItem(item);

    // TODO: Apply current settings for opacity and drop shadow

    // auto-adjust GraphicsView
    m_graphicsView->fitInView(m_graphicsScene->layersBoundingRect(), Qt::KeepAspectRatio);
    m_graphicsView->scale(0.8, 0.8);

    // TODO: factor out setPinList(m_currentPackage->pinList)
    m_pinList = m_currentPackage->pinList;
    m_pinListModel->setPinList(m_pinList);

    // auto-select first layer and first pin
    m_layerListWidget->setCurrentLayer(m_layerListModel->index(0, 0));
    m_pinListView->selectionModel()->setCurrentIndex(m_pinListModel->index(0, 0),
                                                     QItemSelectionModel::ClearAndSelect);
}

void PackageViewer::setCurrentPin(Ipc2581b::Pin *pin)
{
    m_currentPin = pin;
    m_pinDetailModel->setPin(m_currentPin);

    m_graphicsScene->clearForeground();
    m_graphicsScene->addForegroundItem(m_pinToGraphicsItem.value(pin)->clone());
}

void PackageViewer::clear()
{
    m_pinDetailModel->setPin(nullptr);
    m_currentPin = nullptr;
    m_pinListModel->setPinList(QList<Ipc2581b::Pin *>());
    m_pinList = QList<Ipc2581b::Pin *>();
    m_pinToGraphicsItem.clear();
    //m_layerListModel->setLayerList(QList<Ipc2581b::Layer *>());
    //m_layerList = QList<Ipc2581b::Layer *>();
    m_packageDetailModel->setPackage(nullptr);
    m_currentPackage = nullptr;
    m_packageListModel->setPackageList(QList<Ipc2581b::Package *>());
    m_packageList = QList<Ipc2581b::Package *>();
}

QList<Ipc2581b::Layer *> PackageViewer::createLayerList()
{
    QVector<Ipc2581b::Layer *> result(LayerCount);

    auto layer = new Ipc2581b::Layer;
    layer->layerFunction = Ipc2581b::LayerFunction::Courtyard;
    layer->polarity = Ipc2581b::Polarity::Positive;
    layer->side = Ipc2581b::Side::Both;
    layer->name = "Outline";
    result[OutlineLayer] = layer;

    layer = new Ipc2581b::Layer;
    layer->layerFunction = Ipc2581b::LayerFunction::Landpattern;
    layer->polarity = Ipc2581b::Polarity::Positive;
    layer->side = Ipc2581b::Side::Both;
    layer->name = "LandPattern";
    result[LandPatternLayer] = layer;

    layer = new Ipc2581b::Layer;
    layer->layerFunction = Ipc2581b::LayerFunction::Pin;
    layer->polarity = Ipc2581b::Polarity::Positive;
    layer->side = Ipc2581b::Side::Both;
    layer->name = "Pins";
    result[PinLayer] = layer;

    layer = new Ipc2581b::Layer;
    layer->layerFunction = Ipc2581b::LayerFunction::Silkscreen;
    layer->polarity = Ipc2581b::Polarity::Positive;
    layer->side = Ipc2581b::Side::Both;
    layer->name = "SilkScreen";
    result[SilkscreenLayer] = layer;

    layer = new Ipc2581b::Layer;
    layer->layerFunction = Ipc2581b::LayerFunction::Assembly;
    layer->polarity = Ipc2581b::Polarity::Positive;
    layer->side = Ipc2581b::Side::Both;
    layer->name = "Assembly";
    result[AssemblyLayer] = layer;

    return result.toList();
}

void PackageViewer::populateOutlineLayer()
{
    auto item = m_graphicsItemFactory->createItem(m_currentPackage->outline);
    item->setFeatureType(GraphicsItemRole::Profile);
    m_graphicsScene->addLayerItem(OutlineLayer, item);
}

void PackageViewer::populateLandPatternLayer()
{
    if (m_currentPackage->landPatternOptional.hasValue)
    {
        auto pattern = m_currentPackage->landPatternOptional.value;

        for (auto pad: pattern->padList)
            m_graphicsScene->addLayerItem(LandPatternLayer, m_graphicsItemFactory->createItem(pad));

        for (auto target: pattern->targetList)
            m_graphicsScene->addLayerItem(LandPatternLayer, m_graphicsItemFactory->createItem(target));
    }
}

void PackageViewer::populatePinLayer()
{
    m_pinToGraphicsItem.clear();
    for (auto pin: m_currentPackage->pinList)
    {
        auto item = m_graphicsItemFactory->createItem(pin);
        m_graphicsScene->addLayerItem(PinLayer, item);
        m_pinToGraphicsItem.insert(pin, item);
    }
}

void PackageViewer::populateSilkscreenLayer()
{
    if (m_currentPackage->silkScreenOptional.hasValue)
    {
        auto silkScreen = m_currentPackage->silkScreenOptional.value;

        for (auto outline: silkScreen->outlineList)
            m_graphicsScene->addLayerItem(SilkscreenLayer, m_graphicsItemFactory->createItem(outline));

        for (auto marking: silkScreen->markingList)
            m_graphicsScene->addLayerItem(SilkscreenLayer, m_graphicsItemFactory->createItem(marking));
    }
}

void PackageViewer::populateAssemblyDrawingLayer()
{
    if (m_currentPackage->assemblyDrawingOptional.hasValue)
    {
        auto assy = m_currentPackage->assemblyDrawingOptional.value;

        if (assy->outline != nullptr)
            m_graphicsScene->addLayerItem(AssemblyLayer, m_graphicsItemFactory->createItem(assy->outline));
        else
            qDebug() << QString("Warning: Package '%1' is missing Assembly Drawing Outline!").arg(m_currentPackage->name);

        for (auto marking: assy->markingList)
            m_graphicsScene->addLayerItem(AssemblyLayer, m_graphicsItemFactory->createItem(marking));
    }
}

void PackageViewer::setDocument(Ipc2581b::Ipc2581 *doc)
{
    m_ipc = doc;

    clear();

    if (m_ipc == nullptr)
        return;

    m_graphicsItemFactory->loadDictionaries(m_ipc->content);
    m_packageList = m_ipc->ecad->cadData->stepList.first()->packageList;
    m_packageListModel->setPackageList(m_packageList);

    // auto-select first package
    m_packageListView->selectionModel()->setCurrentIndex(m_packageListModel->index(0, 0),
                                                        QItemSelectionModel::ClearAndSelect);
}

QWidget *PackageViewer::centralWidget() const
{
    return m_graphicsView;
}

QWidget *PackageViewer::navigationWidget() const
{
    return m_navigationWidget;
}

QWidget *PackageViewer::auxiliaryWidget() const
{
    return m_auxiliaryWidget;
}

void PackageViewer::enableAntiAlias(bool enabled)
{
    m_graphicsView->enableAntiAliasing(enabled);
}

void PackageViewer::enableTransparentLayers(bool enabled)
{
    m_graphicsScene->enableTransparentLayers(enabled);
}

void PackageViewer::enableDropShadowEffect(bool enabled)
{
    m_graphicsScene->enableDropShadowEffect(enabled);
}

void PackageViewer::enableOpenGL(bool enabled)
{
    m_graphicsView->enableOpenGL(enabled);
}

void PackageViewer::setLevelOfDetails(LevelOfDetails lod)
{
    switch (lod)
    {
        case LevelOfDetails::High:
            m_graphicsScene->setMinimumRenderSize(0.0);
            break;
        case LevelOfDetails::Medium:
            m_graphicsScene->setMinimumRenderSize(1.0);
            break;
        case LevelOfDetails::Low:
            m_graphicsScene->setMinimumRenderSize(2.0);
            break;
    }
}
